<template>

  <div ref="provinceName" id="provinceName">
  </div>
</template>

<script>
import * as THREE from "three";
import * as d3 from 'd3';
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import chinaJson from "../../assets/china.json"
import {onMounted, ref} from 'vue'

let scene, camera, renderer, controls, map, raycaster, mouse, selected, mixer;

let divisions; // 曲线的分段数量
let points; // 返回 分段数量 + 1 个点，例如这里的points.length就为31
let geometryGroup = [];


let colorIndex = 0; // 高亮颜色流动的索引值
let timestamp = 0; // 时间戳
let geometry;
// 墨卡托投影转换
const projection = d3.geoMercator().center([106.412318, 38.909843]).translate([0, 0])
let clock = new THREE.Clock();

let star;
const planeLine = [
  [126.642464, 45.756967], [116.405285, 39.904989],
  [121.472644, 31.231706], [116.405285, 39.904989],
  [114.173355, 22.320048], [116.405285, 39.904989],
  [123.429096, 41.796767], [116.405285, 39.904989],

  [87.617733, 43.792818], [116.405285, 39.904989],
  [91.132212, 29.660361], [116.405285, 39.904989],
  [101.778916, 36.623178], [116.405285, 39.904989],
  [102.712251, 25.040609], [116.405285, 39.904989],
  [103.823557, 36.058039], [116.405285, 39.904989],
  [111.670801, 40.818311], [116.405285, 39.904989]
]


export default {
  setup() {
    // 省份名称展示div
    let provinceName = ref(null);
    // 是否首次进入，首次不显示省份名称
    let firstIn = ref(true);
    onMounted(() => {
      initScene();
      initPlaneLine(planeLine)
      initMap(chinaJson)
      initLight(planeLine)
      initStar()
      initCamera();
      setRaycaster()
      animate();

      function initStar() {
        let geom = new THREE.Geometry();
        let material = new THREE.ParticleBasicMaterial({
          size: 2,
          vertexColors: true
        });
        let n = 1200;
        for (let i = 0; i < 3000; i++) {
          let particle = new THREE.Vector3(
              (Math.random() - 0.5) * n,
              (Math.random() - 0.5) * n,
              (Math.random() - 0.5) * n
          );
          geom.vertices.push(particle);
          let color_k = Math.random();
          geom.colors.push(new THREE.Color(color_k, color_k, color_k * 2.0));
        }
        star = new THREE.ParticleSystem(geom, material);
        scene.add(star)
      }
      /**
       * 初始化光柱
       */
      function initLight(planeLine) {
        let meshGroup = new THREE.Group();
        let times = [0, 10]; //关键帧时间数组，离散的时间点序列
        let playArr = [];
        for (let i = 0; i < planeLine.length; i++) {
          let plane = new THREE.ConeGeometry(1, 1.5);
          let material = new THREE.MeshPhongMaterial({
            transparent: true,
            specular: '#2cfd3f',
            color: '#2cfd3f',
            shininess: 12
          });
          let mesh = new THREE.Mesh(plane, material);
          mesh.rotateX(Math.PI / -2)
          mesh.name = "Cone" + i;
          const [x, y] = projection([planeLine[i][0], planeLine[i][1]]); // 点1坐标
          mesh.position.set(x, -y, 13)
          let geometry = new THREE.Geometry(); //声明一个几何体对象Geometry
          let R = 1; //圆弧半径
          let N = 150; //分段数量
          // 批量生成圆弧上的顶点数据
          for (let i = 0; i < N; i++) {
            let angle = 2 * Math.PI / N * i;
            let x1 = R * Math.sin(angle);
            let y1 = R * Math.cos(angle);
            geometry.vertices.push(new THREE.Vector3(x1, y1, 0));
          }
          // 插入最后一个点，line渲染模式下，产生闭合效果
          geometry.vertices.push(geometry.vertices[0])
          //材质对象
          let materialC = new THREE.LineBasicMaterial({
            color: '#2cfd3f'
          });
          //线条模型对象
          let line = new THREE.Line(geometry, materialC);
          line.name = 'CityLine' + i;
          line.position.set(x, -y, 10)

          let values = [x, -y, 13, x, -y, 11]; //与时间点对应的值组成的数组
          // 创建位置关键帧对象：0时刻对应位置0, 0, 0   10时刻对应位置150, 0, 0
          let posTrack = new THREE.KeyframeTrack('Cone' + i + '.position', times, values);
          // 创建名为Sphere对象的关键帧数据  从0~20时间段，尺寸scale缩放3倍
          let scaleTrack = new THREE.KeyframeTrack('CityLine' + i + '.scale', [0, 20], [1, 1, 1, 3, 3, 3]);
          playArr.push(posTrack, scaleTrack)
          meshGroup.add(line, mesh)
        }
        scene.add(meshGroup)


        // duration决定了默认的播放时间，一般取所有帧动画的最大时间
        // duration偏小，帧动画数据无法播放完，偏大，播放完帧动画会继续空播放
        let duration = 20;
        // 多个帧动画作为元素创建一个剪辑clip对象，命名"default"，持续时间20
        let clip = new THREE.AnimationClip("default", duration, playArr);

        /**
         * 播放编辑好的关键帧数据
         */
        // group作为混合器的参数，可以播放group中所有子对象的帧动画
        mixer = new THREE.AnimationMixer(meshGroup);
        // 剪辑clip作为参数，通过混合器clipAction方法返回一个操作对象AnimationAction
        let AnimationAction = mixer.clipAction(clip);
        //通过操作Action设置播放方式
        AnimationAction.timeScale = 20;//默认1，可以调节播放速度
        // AnimationAction.loop = THREE.LoopOnce; //不循环播放
        AnimationAction.play();//开始播放

      }

      /**
       * 初始化飞机航线
       */
      function initPlaneLine(planeLine) {
        for (let i = 0; i < planeLine.length; i += 2) {

          const [x, y] = projection([planeLine[i][0], planeLine[i][1]]); // 点1坐标
          const point1 = [x, -y, 10]
          const [n, m] = projection([planeLine[i + 1][0], planeLine[i + 1][1]])
          const point2 = [n, -m, 10]; // 点2坐标
          const controlPoint = [(x + n) / 2, (-y + -n) / 2, 20]; // 控制点坐标

          // 创建三维二次贝塞尔曲线
          let curve = new THREE.QuadraticBezierCurve3(
              new THREE.Vector3(point1[0], point1[1], point1[2]),
              new THREE.Vector3(controlPoint[0], controlPoint[1], controlPoint[2]),
              new THREE.Vector3(point2[0], point2[1], point2[2])
          );
          divisions = 10; // 曲线的分段数量
          points = curve.getPoints(divisions); // 返回 分段数量 + 1 个点，例如这里的points.length就为31
          geometry = new THREE.Geometry();
          geometry.vertices = points;
          // 设置顶点 colors 数组，与顶点数量和顺序保持一致。
          geometry.colors = new Array(points.length).fill(
              new THREE.Color('#333300')
          );
          // 生成材质
          let material = new THREE.LineBasicMaterial({
            vertexColors: THREE.VertexColors, // 顶点着色
            transparent: true, // 定义此材质是否透明
            side: THREE.DoubleSide,
            opacity: 0.8,
            linewidth: 10
          });
          /**
           * 航线网格
           */
          const mesh = new THREE.Line(geometry, material);
          scene.add(mesh);
          geometryGroup.push(geometry);
        }

      }

      /**
       * 初始化地图信息
       * @param chinaJson
       */
      function initMap(chinaJson) {
        // 创见一个3d对象
        map = new THREE.Object3D();

        // 遍历地图json
        chinaJson.features.forEach(elem => {
          // 创建省份3d对象
          const province = new THREE.Object3D();
          // 坐标数组
          const coordinates = elem.geometry.coordinates;
          // 循环坐标数组
          // 将geo的属性放到省份模型中

          coordinates.forEach(multiPolygon => {
            // 处理内蒙古坐标数组的特殊情况
            if (elem.geometry.type === 'Polygon') {
              multiPolygon = [multiPolygon];
            }
            multiPolygon.forEach(polygon => {
              const shape = new THREE.Shape();
              // 创建线材质，白色
              const lineMaterial = new THREE.LineBasicMaterial({color: 'white'});

              // 创建Geometry
              const lineGeometry = new THREE.BufferGeometry()
              // 创建位置数组
              const positionArray = new Array();
              for (let i = 0; i < polygon.length; i++) {
                const [x, y] = projection(polygon[i]);
                if (i === 0) {
                  shape.moveTo(x, -y);
                }
                shape.lineTo(x, -y);
                // 填充位置数组，指定3d模型高度
                positionArray.push(new THREE.Vector3(x, -y, 10.00))
              }
              // 设置位置数组到lineGeometry
              lineGeometry.setFromPoints(positionArray)
              // 指定几何属性
              const extrudeSettings = {
                depth: 10,// 总体高度
                bevelEnabled: false
              };
              // 创建一个3d几何图形
              const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)
              // 创建材质1 指定颜色、透明度，几何表面
              const material = new THREE.MeshBasicMaterial({color: '#5dd9ec', transparent: true, opacity: 0.6})
              // 创建材质2 指定颜色、透明度，几何内部
              const material1 = new THREE.MeshBasicMaterial({color: '#5dd9ec', transparent: true, opacity: 0.6})
              // 创建网格
              const mesh = new THREE.Mesh(geometry, [material, material1])
              // 创建线
              const line = new THREE.Line(lineGeometry, lineMaterial)
              // 添加到3d对象中
              province.add(mesh)
              province.add(line)
            })
          })
          province.properties = elem.properties;
          if (elem.properties.contorid) {
            const [x, y] = projection(elem.properties.contorid);
            province.properties._centroid = [x, y];
          }
          // 添加省份到map
          map.add(province);
        })
        // 添加省份map到场景
        scene.add(map);
      }

      /**
       * 创建场景对象Scene
       */
      function initScene() {
        scene = new THREE.Scene();
        /**
         * 光源设置
         */
            //环境光
        let ambient = new THREE.AmbientLight(0xffffff);

        scene.add(ambient);
        // 辅助坐标系  参数250表示坐标系大小，可以根据场景大小去设置
        // let axesHelper = new THREE.AxesHelper(100);
        // scene.add(axesHelper);
      }

      /**
       * 初始化相机
       */
      function initCamera() {
        /**
         * 相机设置
         */
            //窗口宽度
        let width = window.innerWidth;
        //窗口高度
        let height = window.innerHeight;
        //窗口宽高比
        let k = width / height;
        //三维场景显示范围控制系数，系数越大，显示的范围越大
        let s = 70;
        //创建相机对象
        camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
        //设置相机位置
        camera.position.set(0, -100, 300);
        //设置相机方向(指向的场景对象)
        camera.lookAt(scene.position);

        initRenderer(width, height);
      }


      /**
       * 创建渲染器对象
       */
      function initRenderer(width, height) {
        renderer = new THREE.WebGLRenderer();
        //设置渲染区域尺寸
        renderer.setSize(width, height);
        //设置背景颜色
        renderer.setClearColor('#343333', 1);
        //body元素中插入canvas对象
        document.body.appendChild(renderer.domElement);
        // 鼠标，键盘控制
        //创建控件对象
        controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true; // 启用阻尼（惯性），这将给控制器带来重量感，如果该值被启用，必须在动画循环里调用.update()
        controls.dampingFactor = 0.05;
        controls.update();
        //监听鼠标、键盘事件
        controls.addEventListener('change', render);
      }

      /**
       * 执行渲染操作
       */
      function render() {
        renderer.render(scene, camera);
        //clock.getDelta()方法获得两帧的时间间隔
        // 更新混合器相关的时间
        mixer.update(clock.getDelta());
      }

      /**
       * 设置Raycaster
       */
      function setRaycaster() {
        // 初始化Raycaster
        raycaster = new THREE.Raycaster()
        // 初始化鼠标事件
        mouse = new THREE.Vector2()
        const onMouseMove = (event) => {
          // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
          mouse.x = (event.clientX / window.innerWidth) * 2 - 1
          mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
          // 更改div位置
          provinceName.value.style.left = event.clientX + 2 + 'px'
          provinceName.value.style.top = event.clientY + 2 + 'px'
          // 设置首次访问为false
          firstIn.value = false;
        }
        // 添加鼠标移动监听事件
        window.addEventListener('mousemove', onMouseMove, false)
      }

      /**
       * 航线动画
       */
      function planeAnimate() {
        // controls.enableDamping设为true时（启用阻尼），必须在动画循环里调用.update()
        controls.update();
        // 时间间隔
        let now = new Date().getTime();
        if (now - timestamp > 50) {
          geometryGroup.forEach(g => {
            g.colors = new Array(divisions + 1)
                .fill(new THREE.Color('#06ee12'))
                .map((color, index) => {
                  if (index === colorIndex) {
                    return new THREE.Color('#fd8802');
                  }
                  return color;
                });
            // 如果geometry.colors数据发生变化，colorsNeedUpdate值需要被设置为true
            g.colorsNeedUpdate = true;
          })

          timestamp = now;
          colorIndex++;
          if (colorIndex > divisions) {
            colorIndex = 0;
          }
        }
      }

      /**
       * 鼠标移动后的变化
       */
      function animate() {
        requestAnimationFrame(animate.bind(this))
        // 通过摄像机和鼠标位置更新射线
        raycaster.setFromCamera(mouse, camera)
        // 算出射线 与当场景相交的对象有那些
        const intersects = raycaster.intersectObjects(
            scene.children,
            true
        )
        // 恢复地图原本颜色
        if (selected) {
          selected.object.material[0].color.set('#5dd9ec')
          selected.object.material[1].color.set('#5dd9ec')
          provinceName.value.style.visibility = 'hidden'
        }
        selected = null
        selected = intersects.find(
            (item) => item.object.material && item.object.material.length === 2
        )
        if (selected) {
          if (!firstIn.value) {
            selected.object.material[0].color.set('#a1f5c4')
            selected.object.material[1].color.set('#a1f5c4')
            // 展示省份名称
            showTip()
          }
        }
        planeAnimate()

        star.rotation.x += 0.00005;
        star.rotation.y += 0.00005;
        star.rotation.z += 0.00005;
        render()
      }

      /**
       * 展示省份名称
       */
      function showTip() {
        // 显示省份名称
        if (selected) {
          const properties = selected.object.parent.properties
          // 设置名称
          provinceName.value.textContent = properties.name
          // 显示div
          provinceName.value.style.visibility = 'visible'
        } else {
          // 隐藏div
          provinceName.value.style.visibility = 'hidden'
        }
      }

      // render()
    });
    return {provinceName, firstIn}
  }
}
</script>
<style scoped>
#provinceName {
  position: absolute;
  z-index: 2;
  background: white;
  padding: 10px;
  border-radius: 2px;
  visibility: hidden;
}
</style>